import errMagFun from "./errMsg";
import { rstoreAnyType, typeReact, DataType, rStoreProtoType } from "../types";
import { rStatus } from "../prototype";

const responseCallbackFun =
  (_this: rstoreAnyType | rStatus<any>) => (fun: Function) => {
    if (_this.reactive_callback_function_) {
      _this.reactive_callback_function_.push(fun);
    }
  };

const Response = (
  _this: rstoreAnyType | rStatus<any>,
  key: Array<string> | string
) => {
  const { reactive_callback_function_, the_react_instance_array_ } = _this;
  if (reactive_callback_function_ && the_react_instance_array_) {
    reactive_callback_function_.forEach((item) => item(_this.data));
    const responseArr = the_react_instance_array_.filter((firstItem) => {
      if (key instanceof Array) {
        return key.some((secendItme) => {
          return firstItem.keys.indexOf(secendItme) !== -1;
        });
      } else {
        return firstItem.keys.indexOf(key) !== -1;
      }
    });
    responseArr.forEach((item) => {
      item._react.forceUpdate();
    });
  }
  return true;
};

const creatNewData = <T extends DataType>(
  _this: rstoreAnyType | rStatus<T>,
  newData: T
): T => {
  const data = newData;
  let fluterKeys: Array<string> = [];
  let responseFloorKeys: Array<string> = [];
  _this.reactive_callback_function_ = [];
  _this.the_react_instance_array_ = [];
  const handler = {
    get(obj: any, key: string): any {
      if (
        typeof obj === "object" &&
        typeof obj[key] === "object" &&
        obj[key] !== null
      ) {
        if (responseFloorKeys.length === 0) responseFloorKeys.push(key);
        return new Proxy(obj[key], handler);
      }
      const resultType = obj[key];
      return Reflect.get(obj, key) as typeof resultType;
    },
    set(obj: any, prop: string, value: any): any {
      if (responseFloorKeys[0]) {
        Reflect.set(obj, prop, value);
        if (
          (obj instanceof Array && prop === "length") ||
          obj instanceof Object
        ) {
          Response(_this, responseFloorKeys[0]);
          responseFloorKeys = [];
        }
        return true;
      }
      if (typeof value === "object") {
        const keys = Object.keys(value);
        if (keys.length > 0 && keys.indexOf("_rstore_special_character_")) {
          if (keys.indexOf("_rstore_special_character_") !== -1) {
            fluterKeys.push(prop);
            Reflect.set(obj, prop, value.value);
            if (keys.indexOf("_rstore_immediately_character_") !== -1) {
              Response(_this, fluterKeys);
              fluterKeys = [];
            }
            return true;
          }
        }
      }
      if (obj[prop] === value) return true;
      Reflect.set(obj, prop, value);
      return Response(_this, prop);
    },
    deleteProperty(obj: any, prop: string) {
      if (prop.indexOf("_rstore_special_character_") !== -1) {
        const propsCount = prop.replace("_rstore_special_character_", "");
        fluterKeys.push(propsCount);
        return Reflect.deleteProperty(obj, propsCount);
      }
      fluterKeys.push(prop);
      Reflect.deleteProperty(obj, prop);
      return Response(_this, fluterKeys);
    },
  };
  return new Proxy(data, handler) as T;
};

const bindDataFun =
  (_this: rstoreAnyType | rStatus<any>) =>
  (dataKay: Array<string>, _instance: typeReact) => {
    const { the_react_instance_array_ } = _this;
    if (the_react_instance_array_) {
      if (dataKay.length === 0) return;
      if (
        the_react_instance_array_.every((firstItem) => {
          if (firstItem) {
            if (firstItem._react === _instance) {
              dataKay.forEach((secendItem) => {
                if (firstItem.keys.indexOf(secendItem) === -1) {
                  firstItem.keys.push(secendItem);
                }
              });
              return false;
            }
          }
          return true;
        })
      ) {
        const keys = dataKay instanceof Array ? [...dataKay] : [dataKay];
        the_react_instance_array_.push({ _react: _instance, keys });
      }
    }
  };

const unBindDataFun =
  (_this: rstoreAnyType | rStatus<any>) =>
  (keyArr: Array<string>, _instance: typeReact) => {
    if (_this.the_react_instance_array_) {
      _this.the_react_instance_array_ = _this.the_react_instance_array_.filter(
        (item) => {
          if (item._react === _instance) {
            item.keys = item.keys.filter((item) => {
              return keyArr.indexOf(item) === -1;
            });
            if (item.keys.length === 0) {
              return false;
            }
          }
          return true;
        }
      );
    }
  };

const clearDataFun =
  (_this: rstoreAnyType | rStatus<any>) => (dataKeys: Array<string>) => {
    if (dataKeys.length === 0) return errMagFun("clearDataKeysEmpty");
    const dataKay = Object.keys(_this.data);
    let key = dataKeys.filter((item) => dataKay.indexOf(item) !== -1);
    key.forEach((item, index) => {
      if (index === key.length - 1) return delete _this.data[item];
      delete _this.data[`${item}_rstore_special_character_`];
    });
  };

const changeDataFun =
  <S extends { [name: string]: any }>(_this: rstoreAnyType | rStatus<any>) =>
  (obj: S) => {
    const dataKay = Object.keys(_this.data);
    let key = Object.keys(obj).filter((item) => dataKay.indexOf(item) !== -1);
    key.forEach((item, index) => {
      type resultType = {
        _rstore_special_character_: boolean;
        value: any;
        _rstore_immediately_character_?: boolean;
      };
      const result: resultType = {
        _rstore_special_character_: true,
        value: obj[item],
      };
      if (index === key.length - 1)
        result._rstore_immediately_character_ = true;
      _this.data[item] = result;
    });
  };

export default <T extends DataType>(
  _this: rStoreProtoType<T> | rStatus<T>,
  Data: T
) => {
  _this.data = creatNewData(_this, Data);
  _this.clearData = clearDataFun(_this);
  _this.changeData = changeDataFun<Partial<T>>(_this);
  _this.bindData = bindDataFun(_this);
  _this.unBindData = unBindDataFun(_this);
  _this.responseCallback = responseCallbackFun(_this);
};
